# 继承和多态

'''
在OOP程序设计中，当我们定义一个class的时候，可以从某个现有的class继承，新的class称为子类（SubClass），而被继承的class称为基类、父类或超类（Base class、Super class）
'''

# 比如，我们已经编写了一个名为 Animal 的class,有个run()方法可以直接打印
class Animal(object):
    def run(self):
        print('animal is running...')
# 当我们需要编写 Dog 和 Cat 类时，就可以直接从 Animal 类继承：
class Dog(Animal):
    pass
class Cat(Animal):
    pass

# 对于 Dog 来说，Animal 就是它的父类，对于 Animal 来说，Dog 就是它的子类
'''
继承有什么好处？
最大的好处就是子类获得了父类的全部功能。由于Animal实现了run()方法，因此，Dog 和 Cat 作为它的子类，什么事也没干，就自动拥有了run()方法
'''
dog = Dog()
dog.run()
cat = Cat()
cat.run()
# 当然，也可以对子类增加一些方法
class Dog(Animal):
    def run(self):
        print('Dog is running...')
    def eat(self):
        print('Eating meat...')

class Cat(Animal):
    def run(self):
        print('Cat is running...')
    def eat(self):
        print('Eating fish...')
dog = Dog()
dog.run()
dog.eat()
cat = Cat()
cat.run()
cat.eat()
"""
当子类和父类都存在相同的 run() 方法时，我们说，子类的 run() 覆盖了父类的 run()，在代码运行的时候，总是会调用子类的 run()。这样，我们就获得了继承的
另一个好处：多态

要理解什么是多态，我们首先要对数据类型再做一点说明。当我们定义一个class的时候，我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型
比如：str、list、dict没什么两样：
"""
a = list() # a 是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型

# 判断一个变量是否是某种类型可以用 isinstance()判断
print(isinstance(a, list))
print(isinstance(b, Animal))
print(isinstance(c,  Dog))
print(isinstance(c,  Animal)) # True c 还是 Animal 因为 Dog继承了Animal
print(isinstance(b,  Dog)) # False Dog 可以改成是Animal, 但是反过来不行，Animal不能看成Dog

print('-'* 100)

# 要理解多态的好处，我们还需要再编写一个函数，这个函数接受一个 Animal 类型的变量
def run_twice(animal):
    animal.run()
    animal.run()
ani = Animal()
run_twice(ani)

print('-'* 100)

dog = Dog()
run_twice(dog)

print('-'* 100)

'''
你会发现，新增一个 Animal 的子类，不必对 run_twice 做任何修改，实际上，任何依赖Animal作为参数的函数或者方法都可以不加任何修改正常运行
原因就在于多态。

多态的好处就是，当我们需要传入 Dog/Cat时，我们只需要接收 Animal 类型就可以了，因为 Dog、Cat都是 Animal类型，然后，按照 Animal 类型
进行操作即可。由于Animal类型有run()方法，因此，传入的任意类型，只要是Animal类或者子类，就会自动调用时间类型的run()方法，这就是多态的思想

对于一个变量，我们只需要知道它是Animal类型，无需确切的知道它的子类型，就可以放心调用run()方法，而具体调用的 run 方法是作用在Animal、Dog、Cat 对象上
由运行时该对象的确切类型决定，这就是多态真正的威力：调用方只管调用，不管细节，而当我们新增一种Animal的子类时，只要确保run()方法编写正确，不用管
原来代码是如何调用的。这就是著名的“开闭”原则：

对扩展开发：运行新增Animal子类

对修改封闭：不需要修改依赖 Animal类型的 run_twice()等函数

继承还可以一级一级地继承下来，就好比从爷爷到爸爸、再到儿子这样的关系。而任何类，最终都可以追溯到根类object, 这些继承关系看上去就像一颗倒着的树


'''

# 静态语言 VS 动态语言
'''
对于静态语言（例如Java）来说，如果需要传入Animal类型，则传入的对象必须是Animal类型或者它的子类，否则，将无法调用run()方法。

对于Python这样的动态语言来说，则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了：
class Timer(object):
    def run(self):
        print('Start...')

这就是动态语言的“鸭子类型”，它并不要求严格的继承体系，一个对象只要“看起来像鸭子，走起路来像鸭子”，那它就可以被看做是鸭子。

Python的“file-like object“就是一种鸭子类型。对真正的文件对象，它有一个read()方法，返回其内容。但是，许多对象，只要有read()方法，都被视为“file-like object“。许多函数接收的参数就是“file-like object“，你不一定要传入真正的文件对象，完全可以传入任何实现了read()方法的对象。
'''